#include "stdafx.h"
#include "ZobristHasher.h"

#include <random>
#include "Board.h"
#include "Move.h"
#include <assert.h>

using namespace std;

const unsigned long Z_SEED = 825718776;

// Initialize the constant
const ZobristHasher_t ZobristHasher;

ZobristHasher_t::ZobristHasher_t()
{
	// Create an array large enough for each square to have a distinct value for any given piece
	s_typeVals = make_unique<ulong []>(NUM_TYPES_WB * NUM_SQUARES);

	default_random_engine generator;
	// Always initialize to the same seed so that the engine behavior is deterministic.
	// If we didn't fix the seed, then different key collisions could occur each time we ran
	generator.seed(Z_SEED);

	uniform_int_distribution<ulong> distribution;

	for (size_t i = 0; i < NUM_TYPES_WB * NUM_SQUARES; ++i)
		s_typeVals[i] = distribution(generator);
	_blackMove = distribution(generator);
}

void ZobristHasher_t::Initialize(Board &board) const
{
	ulong hash = 0;

	for (size_t i = 0; i < NUM_SQUARES; ++i)
	{
		Piece &p = board.Squares[i];

		if (!p)
			continue;

		hash ^= s_typeVals[GetPieceBaseIdx(p.Type, p.Color) + i];
	}

	board.ZobristHash = hash;
}

void ZobristHasher_t::DoMove(Board &board, const Move &move) const
{
	// Erase the first piece from the board
	size_t p1base = GetPieceBaseIdx(move.Piece1.Type, move.Piece1.Color), p2base;
	board.ZobristHash ^= s_typeVals[p1base + move.SrcPos1];
	
	// If the move is a castle, it means that 2 pieces are actually moving, so we also need to erase the
	// second.
	// If it's a capture, then remove the captured piece from the board
	switch (move.Type)
	{
	case MoveType::Castle:
		p2base = GetPieceBaseIdx(PieceType::Rook, move.Piece1.Color);
		board.ZobristHash ^= s_typeVals[p2base + move.SrcPos2];
		board.ZobristHash ^= s_typeVals[p2base + move.DestPos2];
		break;
	case MoveType::Capture:
		// Remove the captured piece from the board
		board.ZobristHash ^= s_typeVals[GetPieceBaseIdx(move.Captured, other_color(move.Piece1.Color)) + move.DestPos1];
		break;
	case MoveType::EnPassant:
		// Remove the pawn from the board using the DestPos2
		board.ZobristHash ^= s_typeVals[GetPieceBaseIdx(move.Captured, other_color(move.Piece1.Color)) + move.DestPos2];
		break;
	}

	// Now add the first piece back in the new position.
	// Need to handle the special case of pawn promotion
	if (move.Flags & PROMOTED)
		board.ZobristHash ^= s_typeVals[GetPieceBaseIdx(PieceType::Queen, move.Piece1.Color) + move.DestPos1];
	else
		board.ZobristHash ^= s_typeVals[p1base + move.DestPos1];

	board.ZobristHash ^= _blackMove;
}


void ZobristHasher_t::UndoMove(Board &board, const Move &move) const
{
	// XOR is symmetric, so just call the move again
	DoMove(board, move);

	//// Do the reverse
	//size_t p1base = GetPieceBaseIdx(move.Piece1.Type, move.Piece1.Color), p2base;

	//if (move.Piece1.Type != PieceType::Pawn || (move.DestPos1 >= 8 && move.DestPos1 <= 55))
	//	board.ZobristHash ^= s_typeVals[p1base + move.DestPos1];
	//else
	//	board.ZobristHash ^= s_typeVals[GetPieceBaseIdx(PieceType::Queen, move.Piece1.Color) + move.DestPos1];

	//switch (move.Type)
	//{
	//case MoveType::Castle:
	//	p2base = GetPieceBaseIdx(PieceType::Rook, move.Piece1.Color);
	//	board.ZobristHash ^= s_typeVals[p2base + move.DestPos2];
	//	board.ZobristHash ^= s_typeVals[p2base + move.SrcPos2];
	//	break;
	//case MoveType::Capture:
	//	board.ZobristHash ^= s_typeVals[GetPieceBaseIdx(move.Captured, other_color(move.Piece1.Color)) + move.DestPos1];
	//	break;
	//}

	//board.ZobristHash ^= s_typeVals[p1base + move.SrcPos1];
}

size_t ZobristHasher_t::GetPieceBaseIdx(PieceType type, PieceColor color) const
{
	size_t idx = (size_t) type;
	
	// Using switch here as a micro-optimization since it should run a bit faster than
	// branching
	switch (color)
	{
	case PieceColor::Black:
		idx += NUM_TYPES;
	}

	return idx * NUM_SQUARES;
}



